﻿import numpy
import scipy.special
import os
import ModelOpreation 

# 神经网络class定义
class neuralNetwork():
    
    
    # 初始化神经网络必须的参数（构造函数）
    def __init__(self, inputnodes, hiddennodes, outputnodes, learningrate):
        # 设置输入、隐藏、输出层的节点数
        self.inodes = inputnodes
        self.hnodes = hiddennodes
        self.onodes = outputnodes
        
        # 随机初始化链接权重矩阵，wih与who
        # 矩形数组中的权重是w_i_j，即从i节点到下一层的j节点的链接
        # random.normal表示以正太分布取样，其中均值为0，标准方差为节点传入链接数目的开方。
        # (self.hnodes, self.inodes)表示初始化矩阵的行列数
        self.wih = numpy.random.normal(0.0, pow(self.hnodes, -0.5), (self.inodes, self.hnodes))#[输入层->隐藏层]矩阵：[784,200]
        self.who = numpy.random.normal(0.0, pow(self.onodes, -0.5), (self.hnodes, self.onodes))#[隐藏层->输出层]矩阵：[200,10]
        # 学习率
        self.lr = learningrate
        # 激活函数是sigmoid函数
        #self.activation_function = lambda x: scipy.special.expit(x)
        pass

    # 激活函数是sigmoid函数
    def activation_function(self,x):
        pass
        return scipy.special.expit(x)
        
    """
    网络内部计算流程示例
    格式化输入
    (784,)->(1,784)
    格式化输出
    (10,)->(1,10)

    前向传播 [输入层->隐藏层]，求和后激活
    (1,784) dot (784,200)  = (1,200)
    sigmoid (1,200)
    前向传播 [隐藏层->输出层]，求和后激活
    (1,200) dot (200,10)=(1,10)
    sigmoid (1,10)

    计算输出层损失
    (1,10)-(1,10)
    计算（分摊）隐藏层损失
    (1,10) dot (200,10).T=(1,200)

    
    更新[隐藏层->输出层]权重矩阵
    (200,10)+(1,200).T dot (1,10)*(1,10)*[1-(1,10)]=(200,10)+(200,1)dot(1,10)=(200,10)
    更新[输入层->隐藏层]权重矩阵
    (784,200)+(1,784).T dot(1,200)*(1,200)*[1-(1,200)]=(784,200)+(784,1)dot(1,200)=(784,200)
    """
    # 网络架构
    def NetworkStructure(self, inputs_list, targets_list):
        #处理输入数据
        # 将长为(784,)的inputs_list列表，转换为(1，784)的2维数组
        inputs = numpy.array(inputs_list, ndmin = 2)
        # 将长为(10,)的targets_list列表，转换为(1，10)的2维数组
        targets = numpy.array(targets_list, ndmin = 2)

        #正向传播部分
        # 计算 输入层->隐藏层 的信号(1,784) dot (784,200)=(1,200)
        hidden_inputs = numpy.dot(inputs, self.wih)
        # 通过隐藏层激活函数发出的信号
        hidden_outputs = self.activation_function(hidden_inputs)
        
        # 计算 隐藏层->输出层 的信号(1,200)dot(200,10)=(1,10)
        final_inputs = numpy.dot(hidden_outputs, self.who)
        # 通过输出层激活函数发出的信号(10,1)
        final_outputs = self.activation_function(final_inputs)
        
        # 计算误差部分
        # 计算输出层与真实值间的误差(target - actual)=(1,10)-(1,10)
        output_errors = targets - final_outputs
        # 通过矩阵乘法，按 [隐藏层->输出层] 权重矩阵比例，将输出层误差分配到隐藏层中的200个节点上，(1,10) dot (200,10).T=(1,200)
        hidden_errors = numpy.dot(output_errors, self.who.T)
        
        # 权重更新部分
        # 更新 [隐藏层->输出层] 之间权重，隐藏层与输出层之间梯度=隐藏层权重的转置 dot 输出层误差的偏导数=[Eo*So(1-So)] dot Oh
        # =[隐藏层->输出层]+[隐藏层输出]dot[输出层误差*激活输出*(1-激活输出)]=(200,10)+(1,200).T dot (1,10)*(1,10)*[1-(1,10)]=(200,10)+(200,1)dot(1,10)=(200,10)
        self.who += self.lr * numpy.dot(numpy.transpose(hidden_outputs), (output_errors * final_outputs * (1.0 - final_outputs)))
        # 更新 [输入层->隐藏层] 之间权重，输入层与隐藏城之间梯度=输入层权重的转置 dot 隐藏层误差的偏导数=[Eh*Sh(1-Sh)] dot Oi
        # =[输入层->隐藏层]+[输入层输出]dot[隐藏层误差*激活输出*(1-激活输出)]=(784,200)+(1,784).T dot(1,200)*(1,200)*[1-(1,200)]=(784,200)+(784,1)dot(1,200)=(784,200)
        self.wih += self.lr * numpy.dot(numpy.transpose(inputs), (hidden_errors * hidden_outputs * (1.0 - hidden_outputs)))
        pass
    
    # 调用网络计算
    def NetworkCalculator(self, inputs_list):
        # 将一维(784,)列表inputs_list转换为2维数组(1,784)
        inputs = numpy.array(inputs_list, ndmin = 2)
        # 计算[输入层->隐藏层]的信号(1,784)dot(784,200)=(1,200) 
        hidden_inputs = numpy.dot(inputs, self.wih)
        # 隐藏层激活函数发出的信号(1,200)
        hidden_outputs = self.activation_function(hidden_inputs)
        # 计算[隐藏层->输出层]的信号(1,200)dot(200,10) = (1,10)
        final_inputs = numpy.dot(hidden_outputs, self.who)
        # 输出层激活函数发出的信号(1,10)
        final_outputs = self.activation_function(final_inputs)
        return final_outputs




# 训练网络
def trainNet(input_nodes, hidden_nodes, output_nodes, learning_rate, training_data_list, epochs=2):
    # 实例化一个神经网络的实例
    model = neuralNetwork(input_nodes, hidden_nodes, output_nodes, learning_rate)
    count = 0
    # 对网络循环训练epochs个周期
    for e in range(epochs):
        # 循环遍历训练集，训练网络
        for record in training_data_list:

            # 初始化输入向量，对输入值进行缩放和移位
            # 保证输入值在(0.01,1)范围，避免网络过饱和。加0.01保证节点输入不为0，避免产生死节点
            all_values = record.split(',')
            inputs = (numpy.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01

            # 初始化输出向量，输出为0-9中的一个数字，所以向量维度为10
            # all_values[0]是标签在输出向量中对应的位置
            # 除了输出标签所对应的向量位置是0.99，其余全部为0.01
            targets = numpy.zeros(output_nodes) + 0.01
            targets[int(all_values[0])] = 0.99

            # 用输入、输出数据训练网络
            model.NetworkStructure(inputs, targets)

            count +=1
            if(count%1000 == 0):
                print(count)

            pass
        pass
    return model


# 测试网络
def testNet(model, test_data_list):
    # 记录正确率
    scorecard = []
    # 遍历测试数据集中的所有记录
    for record in test_data_list:
        # 初始化输入向量，对输入值进行缩放和移位
        # 保证输入值在(0.01,1)范围，避免网络过饱和。加0.01保证节点输入不为0，避免产生死节点
        all_values = record.split(',')
        inputs = (numpy.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01

        # 获取每张图对应的真实数字
        correct_label = int(all_values[0])
        
        # 用训练好的网络进行预测，数组中最大值对应的索引作为预测结果
        outputs = model.NetworkCalculator(inputs)
        label = numpy.argmax(outputs)
        
        # 对比预测数字和真实数字是否相同，正确记1，错误记0
        if (label == correct_label):
            scorecard.append(1)
        else:
            scorecard.append(0)
            pass
        pass

    # 求和计算得分
    scorecard_array = numpy.asarray(scorecard)
    print("performance =", scorecard_array.sum() / scorecard_array.size)



# 输入、隐藏、输出层的节点数，学习率
input_nodes = 784
hidden_nodes = 200
output_nodes = 10
learning_rate = 0.1

# 加载训练数据
training_data_file = open(r"mnist_train_100.csv", 'r')
training_data_file = open(os.path.dirname(os.path.dirname(os.getcwd()))+"\BigDataFile\mnist_train.csv", 'r')
training_data_list = training_data_file.readlines()
training_data_file.close()

#model = trainNet(input_nodes, hidden_nodes, output_nodes, learning_rate, training_data_list, 5)
#ModelOpreation.saveModel("savemodel.pkl")


# 加载测试数据
test_data_file = open(r"mnist_test_10.csv", 'r')
test_data_file = open(os.path.dirname(os.path.dirname(os.getcwd()))+"\BigDataFile\mnist_test.csv", 'r')
test_data_list = test_data_file.readlines()
test_data_file.close()

model = ModelOpreation.loadModel("savemodel.pkl")
testNet(model, test_data_list)



